import datetime
import numpy as np
from typing import Optional

import coin.proto.coin_market_enums_pb2 as cme_pb2
import coin.support.options_pricing.bs as bs_model


def calculate_implied_volatility(*,
                                 F: float,
                                 K: float,
                                 T: int,
                                 alpha: float,
                                 beta: float,
                                 rho: float,
                                 nu: float,
                                 curr_ts: int) -> Optional[float]:
  if T <= curr_ts:
    return None
  ttm = (T - curr_ts) / 10**9 / (365 * 24 * 60 * 60)
  # if K is at-the-money-forward
  if abs(F - K) < 0.00001:
    numer1 = ((1 - beta)**2)/24*alpha**2/(F**(2 - 2*beta))
    numer2 = 0.25*rho*beta*nu*alpha/(F**(1 - beta))
    numer3 = ((2 - 3*rho**2)/24)*nu**2
    sabrsigma = alpha*(1 + (numer1 + numer2 + numer3)* ttm)/(F**(1-beta))
  else:
    log = np.log(F/K)
    z = (nu/alpha)*((F*K)**((1-beta)/2))*log
    zhi = np.log((np.sqrt(1 - 2*rho*z + z**2) + z - rho)/(1 - rho))
    numer1 = (((1 - beta)**2)/24)*((alpha**2)/((F*K)**(1 - beta)))
    numer2 = 0.25*rho*beta*nu*alpha/((F*K)**((1 - beta)/2))
    numer3 = ((2 - 3*rho**2)/24)*nu**2
    numer = alpha*(1 + (numer1 + numer2 + numer3)* ttm)*z
    denom1 = ((1 - beta)**2/24)*(log**2)
    denom2 = (((1 - beta)**4)/1920)*(log**4)
    denom = ((F*K)**((1 - beta)/2))*(1 + denom1 + denom2)*zhi
    sabrsigma = numer/denom
  return sabrsigma


def calculate_delta(*,
                    F: float,
                    K: float,
                    T: int,
                    r: float,
                    side: int,
                    alpha: float,
                    beta: float,
                    rho: float,
                    nu: float,
                    curr_ts: int) -> Optional[float]:
  if T <= curr_ts:
    return None
  dF = 0.01*F
  vol1 = calculate_implied_volatility(
      F=F+dF, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  vol2 = calculate_implied_volatility(
      F=F-dF, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  V1 = bs_model.calculate_black_price(
      F=F+dF, r=r, vol=vol1, T=T, K=K, side=side, curr_ts=curr_ts)
  V2 = bs_model.calculate_black_price(
      F=F-dF, r=r, vol=vol2, T=T, K=K, side=side, curr_ts=curr_ts)
  delta = (V1 - V2) / (2 * dF)
  return delta
  


def calculate_gamma(*,
                    F: float,
                    K: float,
                    T: int,
                    r: float,
                    side: int,
                    alpha: float,
                    beta: float,
                    rho: float,
                    nu: float,
                    curr_ts: int) -> Optional[float]:
  if T <= curr_ts:
    return None
  dF = 0.01*F
  vol = calculate_implied_volatility(
      F=F, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  vol1 = calculate_implied_volatility(
      F=F+dF, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  vol2 = calculate_implied_volatility(
      F=F-dF, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  V = bs_model.calculate_black_price(
      F=F, r=r, vol=vol, T=T, K=K, side=side, curr_ts=curr_ts)
  V1 = bs_model.calculate_black_price(
      F=F+dF, r=r, vol=vol1, T=T, K=K, side=side, curr_ts=curr_ts)
  V2 = bs_model.calculate_black_price(
      F=F-dF, r=r, vol=vol2, T=T, K=K, side=side, curr_ts=curr_ts)
  gamma = (V1 + V2 - 2 * V) / (dF ** 2)
  return gamma


def calculate_vega(*,
                   F: float,
                   K: float,
                   T: int,
                   r: float,
                   side: int,
                   alpha: float,
                   beta: float,
                   rho: float,
                   nu: float,
                   curr_ts: int) -> Optional[float]:
  if T <= curr_ts:
    return None
  vol = calculate_implied_volatility(
      F=F, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  dvol = 0.005
  vol1 = vol + dvol
  vol2 = max(0.001, vol - dvol)
  V1 = bs_model.calculate_black_price(
      F=F, r=r, vol=vol1, T=T, K=K, side=side, curr_ts=curr_ts)
  V2 = bs_model.calculate_black_price(
      F=F, r=r, vol=vol2, T=T, K=K, side=side, curr_ts=curr_ts)
  # options value change per 1% abs change in volatility
  vega = (V1 - V2) / ((vol1 - vol2) / 0.01)
  return vega


def calculate_theta(*,
                    F: float,
                    K: float,
                    T: int,
                    r: float,
                    side: int,
                    alpha: float,
                    beta: float,
                    rho: float,
                    nu: float,
                    curr_ts: int) -> Optional[float]:
  if T <= curr_ts:
    return None
  vol = calculate_implied_volatility(
      F=F, K=K, T=T, alpha=alpha, beta=beta, rho=rho, nu=nu, curr_ts=curr_ts)
  V = bs_model.calculate_black_price(
      F=F, r=r, vol=vol, T=T, K=K, side=side, curr_ts=curr_ts)
  dT = 10**9 * 60 * 60 * 24
  curr_ts = min(curr_ts + dT, T)
  V2 = bs_model.calculate_black_price(
      F=F, r=r, vol=vol, T=T, K=K, side=side, curr_ts=curr_ts)
  # options value change per passage of 1 day
  theta = V2 - V
  return theta
